package com.loyal.camerax.utils;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.YuvImage;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.ImageProxy;

import com.loyal.camerax.annotation.ImageProcess;
import com.loyal.camerax.config.CompressConfig;
import com.loyal.camerax.config.PhotoConfig;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

public class ImageUtil {
    /**
     * 最大压缩尺寸
     */
    private static final int COMPRESS_SIZE_MAX = 100;
    /**
     * 照片尺寸-大的一侧
     */
    private static final int PICTURE_SITE_MAX = 1280;
    /**
     * 照片尺寸-小的一侧
     */
    private static final int PICTURE_SITE_MIN = 720;
    /**
     * 图片质量
     */
    private static final int PICTURE_QUALITY_MAX = 75;

    public static @Nullable byte[] compressByteArray(@NonNull CompressConfig config) {
        byte[] data = config.getByteDate();
        if (null == data) return null;
        /*不启用压缩，原路返回*/
        boolean compressEnable = config.getEnable();
        if (!compressEnable) {
            return data;
        }
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(data, 0, data.length, options);
            int outWidth = options.outWidth;
            int outHeight = options.outHeight;

            int maxOutSize = Math.max(outHeight, outWidth);
            float scale = 1f * maxOutSize / PICTURE_SITE_MAX;
            int rate = (int) scale;
            if (rate <= 0) {
                rate = 1;
            }
            options.inSampleSize = rate;
            options.inJustDecodeBounds = false;
            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
            int quality = config.getQuality();//PICTURE_QUALITY_MAX
            int interval = CompressConfig.PICTURE_QUALITY_INTERVAL;
            if (null != bitmap) {
                /*按图片大小压缩*/
                bitmap.compress(config.getFormat(), quality, baos);
                // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
                while (baos.toByteArray().length / 1024 > COMPRESS_SIZE_MAX) {
                    // 重置baos即清空baos
                    baos.reset();
                    // 每次都减少5
                    quality -= interval;
                    if (quality < interval) {
                        quality = interval;
                        bitmap.compress(config.getFormat(), quality, baos);
                        break;
                    }
                    bitmap.compress(config.getFormat(), quality, baos);
                }
            }
            return baos.toByteArray();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 将byte[]转Bitmap
     *
     * @param config 压缩配置
     */
    public static @Nullable Bitmap byteArrayToBitmap(@NonNull CompressConfig config) {
        byte[] data = compressByteArray(config);
        if (null == data) return null;
        return BitmapFactory.decodeByteArray(data, 0, data.length);
    }

    /**
     * 添加水印
     */
    public static boolean saveCropImage(byte[] data, String imgPath, String text, boolean needCrop) {
        try {
            CompressConfig config = new CompressConfig();
            config.setEnable(true);
            config.setByteDate(data);
            Bitmap compressBmp = byteArrayToBitmap(config);
            if (null == compressBmp) return false;

            Bitmap bitmap = compressBmp;
            if (needCrop) {
                Matrix m = new Matrix();
                int bmpWidth = compressBmp.getWidth();
                int bmpHeight = compressBmp.getHeight();
                int height = Math.min(bmpHeight, 200);
                int retY = ((Math.max(bmpHeight, 200)) - 200) / 2;
                // 以当前自己的原点旋转多少度
                m.setRotate(0, bmpWidth / 2f, bmpHeight / 2f);
                Bitmap rotatedImage = Bitmap.createBitmap(compressBmp, 0, retY, bmpWidth, height, m, true);
                if (rotatedImage != compressBmp)
                    compressBmp.recycle();
                bitmap = rotatedImage;
            }
            String dateTime = "" /*TimeHelper.getDateTime()*/;
            String mark = String.format("%s\n%s", dateTime, text);
            Log.e("saveCropImage", ": mark=" + mark);
            Bitmap textBmp = textOnBitmap(bitmap, mark);
            boolean success = saveBitmap(textBmp, imgPath, true);
            if (bitmap != null && !bitmap.isRecycled()) {
                bitmap.recycle();
            }
            if (textBmp != null && !textBmp.isRecycled()) {
                textBmp.recycle();
            }
            return success;
        } catch (Exception e) {
            return false;
        }
    }

    public static boolean saveImageByteArray(@NonNull PhotoConfig config, byte[] data) {
        if (null == data) return false;
        try {
            CompressConfig compressConfig = new CompressConfig();
            compressConfig.setEnable(true);
            compressConfig.setByteDate(data);
            Bitmap compressBmp = byteArrayToBitmap(compressConfig);
            if (null == compressBmp) return false;

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(data, 0, data.length, options);
            int outWidth = options.outWidth;
            int outHeight = options.outHeight;

            int rate = 1;
            if (outWidth > outHeight && outWidth > PICTURE_SITE_MAX) {// 如果宽度大的话根据宽度固定大小缩放
                rate = outWidth / PICTURE_SITE_MAX;
            } else if (outWidth < outHeight && outHeight > PICTURE_SITE_MIN) {// 如果高度高的话根据宽度固定大小缩放
                rate = outHeight / PICTURE_SITE_MIN;
            }
            if (rate <= 0) {
                rate = 1;
            }
            options.inSampleSize = rate;
            options.inJustDecodeBounds = false;

            Bitmap sourceBmp = BitmapFactory.decodeByteArray(data, 0, data.length, options);
            Bitmap rotateBmp = createBitmap(sourceBmp, degree);
            boolean result = saveBitmap(rotateBmp, imgPath, true);
            if (null != rotateBmp && !rotateBmp.isRecycled()) {
                rotateBmp.recycle();
            }
            return result;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Bitmap旋转
     *
     * @param degrees 旋转角度
     */
    public static @Nullable
    Bitmap createBitmap(Bitmap bitmap, float degrees) {
        if (null == bitmap) return null;
        try {
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            Matrix matrix = new Matrix();
            // 以当前自己的原点旋转多少度
            matrix.setRotate(degrees, width / 2f, height / 2f);
            //matrix.postScale(1, -1);   //镜像垂直翻转
            //matrix.postScale(-1, 1);   //镜像水平翻转
            Bitmap rotateBmp = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
            if (rotateBmp != bitmap)
                bitmap.recycle();
            return rotateBmp;
        } catch (Exception e) {
            return null;
        }
    }

    /***/
    public static String image2Base64(String imgPath) {
        return image2Base64(new File(imgPath));
    }

    /***/
    public static String image2Base64(File imgFile) {
        try {
            byte[] bytes = file2ByteArray(imgFile);
            // 对字节数组Base64编码
            return Base64.encodeToString(bytes, Base64.DEFAULT);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    public static byte[] file2ByteArray(File file) {
        if (null == file || !file.exists())
            return null;
        byte[] bytes = new byte[1024];
        int len;
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             InputStream in = new FileInputStream(file)) {
            while ((len = in.read(bytes)) != -1) {
                baos.write(bytes, 0, len);
            }
            baos.flush();
            in.close();
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * base64转bitmap
     */
    public static @Nullable
    Bitmap base642Bmp(String base64) {
        if (TextUtils.isEmpty(base64))
            return null;
        byte[] decode = Base64.decode(base64, Base64.DEFAULT);
        return BitmapFactory.decodeByteArray(decode, 0, decode.length);
    }

    /**
     * 质量压缩保存图片文件
     */
    public static boolean saveBitmap(Bitmap bitmap, String imgPath, boolean compress) {
        if (null == bitmap || TextUtils.isEmpty(imgPath))
            return false;
        File jpgFile = new File(imgPath);

        try (FileOutputStream outputStream = new FileOutputStream(jpgFile)) {
            outputStream.write(compress(bitmap, compress));
            outputStream.flush();
            outputStream.close();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 压缩
     */
    public static @Nullable byte[] compress(Bitmap bitmap, boolean compress) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            // 质量压缩方法，这里100表示不压缩，把压缩后的数据存放到baos中
            int quality = PICTURE_QUALITY_MAX;

            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
            if (compress) {
                // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
                while (baos.toByteArray().length / 1024 > COMPRESS_SIZE_MAX) {
                    // 重置baos即清空baos
                    baos.reset();
                    // 每次都减少10
                    quality -= 5;
                    // 这里压缩options%，把压缩后的数据存放到baos中
                    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
                }
            }
            return baos.toByteArray();
        } catch (Exception e) {
            return null;
        }
    }

    public static @Nullable Bitmap textOnBitmap(Bitmap bitmap, String text) {
        if (null == bitmap) return null;
        try {
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(newBitmap);
            canvas.drawBitmap(bitmap, 0, 0, null);
            bitmap.recycle();
            TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
            textPaint.setColor(Color.RED);
            int textSize = 45;
            textPaint.setTextSize(textSize);
            textPaint.setTypeface(Typeface.DEFAULT_BOLD);
            //根据图片宽度设置文字文字，使文字居右底端显示
            int newStringWidth = (int) (width * 0.8f);
            // 文字矩阵区域
            Rect textBounds = new Rect();
            textPaint.getTextBounds(text, 0, text.length(), textBounds);

            //计算文字坐标 预留20个像素
            int stringX = 15;
            int stringY = 15;
            //canvas.drawText(text, stringX, stringY, paint);

            StaticLayout layout = new StaticLayout(text, textPaint, width / 2,
                    Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
            // 画文字
            canvas.translate(stringX, stringY);
            layout.draw(canvas);
            //保存所有元素
            canvas.save();
            canvas.restore();
            return newBitmap;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 加密，把一个字符串在原有的基础上+1
     *
     * @param data 需要解密的原字符串
     * @return 返回解密后的新字符串
     */
    public static String encode(String data) {
        if (TextUtils.isEmpty(data))
            return "";
        try {
            byte[] b = data.getBytes();
            for (int i = 0; i < b.length; i++) {
                b[i] += 1;//在原有的基础上+1
            }
            return new String(b);
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * 解密：把一个加密后的字符串在原有基础上-1
     *
     * @param data 加密后的字符串
     * @return 返回解密后的新字符串
     */
    public static String decode(String data) {
        if (TextUtils.isEmpty(data))
            return "";
        try {
            byte[] b = data.getBytes();
            //遍历
            for (int i = 0; i < b.length; i++) {
                b[i] -= 1;//在原有的基础上-1
            }
            return new String(b);
        } catch (Exception e) {
            return "";
        }
    }

    public static String buildImgName(String zpDm) {
        return String.format("%s.jpg", zpDm);
    }

    /**
     * Bitmap旋转
     *
     * @param degrees 旋转角度
     */
    public static @Nullable
    Bitmap rotateBitmap(Bitmap bitmap, float degrees) {
        if (null == bitmap)
            return null;
        try {
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            Matrix matrix = new Matrix();
            // 以当前自己的原点旋转多少度
            matrix.setRotate(degrees, width / 2f, height / 2f);
            Bitmap rotateBmp = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
            if (rotateBmp != bitmap)
                bitmap.recycle();
            return rotateBmp;
        } catch (Exception e) {
            return null;
        }
    }

    public static boolean save(ImageProxy imageProxy, File saveFile) {
        if (imageProxy == null) {
            return false;
        }
        Bitmap bitmap = imageProxy.toBitmap();
        Bitmap rotationBmp = rotateBitmap(bitmap, imageProxy.getImageInfo().getRotationDegrees());
        //byte[] imageInBuffer = YUV_420_888toNV212(imageProxy);
        return saveBitmap(rotationBmp, saveFile.getPath(), true);
    }
    /*--------------------------------- ImageProxy ---------------------------------*/

    /**
     * YUV_420_888转NV21
     */
    public static @Nullable byte[] yuv420888ToNv21(ImageProxy image, @ImageProcess String mode) {
        byte[] nv21;
        /*第一种方式*/
        if (ImageProcess.MODE_1.equals(mode)) {
            nv21 = bufferToNv21_Mode1(image);
        }
        /*第二种方式*/
        else if (TextUtils.equals(ImageProcess.MODE_2, mode)) {
            nv21 = bufferToNv21_Mode2(image);
        }
        /*第三种方式*/
        else {
            nv21 = yuv420888ToNv21_Mode3(image);
        }
        return nv21;
    }

    /**
     * Semi-Planar格式（SP）的处理和y通道的数据
     */
    private static ByteBuffer getBufferWithoutPadding(ByteBuffer buffer, int width, int rowStride, int times, boolean isVBuffer) {
        if (width == rowStride) return buffer;  //没有buffer,不用处理。
        int bufferPos = buffer.position();
        int cap = buffer.capacity();
        byte[] byteArray = new byte[times * width];
        int pos = 0;
        //对于y平面，要逐行赋值的次数就是height次。对于uv交替的平面，赋值的次数是height/2次
        for (int i = 0; i < times; i++) {
            buffer.position(bufferPos);
            //part 1.1 对于u,v通道,会缺失最后一个像u值或者v值，因此需要特殊处理，否则会crash
            if (isVBuffer && i == times - 1) {
                width = width - 1;
            }
            buffer.get(byteArray, pos, width);
            bufferPos += rowStride;
            pos = pos + width;
        }

        //nv21数组转成buffer并返回
        ByteBuffer bufferWithoutPaddings = ByteBuffer.allocate(byteArray.length);
        // 数组放到buffer中
        bufferWithoutPaddings.put(byteArray);
        //重置 limit 和position 值否则 buffer 读取数据不对
        bufferWithoutPaddings.flip();
        return bufferWithoutPaddings;
    }

    /**
     * Planar格式（P）的处理
     */
    private static ByteBuffer getUVBufferWithoutPaddingP(ByteBuffer uBuffer, ByteBuffer vBuffer, int width, int height, int rowStride, int pixelStride) {
        int pos = 0;
        byte[] byteArray = new byte[height * width / 2];
        for (int row = 0; row < height / 2; row++) {
            for (int col = 0; col < width / 2; col++) {
                int vuPos = col * pixelStride + row * rowStride;
                byteArray[pos++] = vBuffer.get(vuPos);
                byteArray[pos++] = uBuffer.get(vuPos);
            }
        }
        ByteBuffer bufferWithoutPaddings = ByteBuffer.allocate(byteArray.length);
        // 数组放到buffer中
        bufferWithoutPaddings.put(byteArray);
        //重置 limit 和position 值否则 buffer 读取数据不对
        bufferWithoutPaddings.flip();
        return bufferWithoutPaddings;
    }

    /**
     * CameraX默认得到的图片格式是yuv格式的，需要做下转换，FaceDetector只支持Bitmap.Config.RGB_565格式:
     */
    public static @Nullable Bitmap yuv420_888ToRGB555(@NonNull ImageProxy image, int rotationDegrees) {
        byte[] nv21 = yuv420888ToNv21(image, null);
        YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);

        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            yuvImage.compressToJpeg(new Rect(0, 0, image.getWidth(), image.getHeight()), 100, outputStream);
            byte[] jpegArray = outputStream.toByteArray();
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.RGB_565;
            Bitmap bitmap = BitmapFactory.decodeByteArray(jpegArray, 0, jpegArray.length, options);

            Matrix matrix = new Matrix();
            matrix.postRotate(rotationDegrees);
            return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 将Y:U:V == 4:2:2的数据转换为nv21
     *
     * @param y      Y 数据
     * @param u      U 数据
     * @param v      V 数据
     * @param nv21   生成的nv21，需要预先分配内存
     * @param stride 步长
     * @param height 图像高度
     */
    public static void yuv422ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {
        System.arraycopy(y, 0, nv21, 0, y.length);
        // 注意，若length值为 y.length * 3 / 2 会有数组越界的风险，需使用真实数据长度计算
        int length = y.length + u.length / 2 + v.length / 2;
        int uIndex = 0, vIndex = 0;
        for (int i = stride * height; i < length; i += 2) {
            nv21[i] = v[vIndex];
            nv21[i + 1] = u[uIndex];
            vIndex += 2;
            uIndex += 2;
        }
    }

    /**
     * 将Y:U:V == 4:1:1的数据转换为nv21
     *
     * @param y      Y 数据
     * @param u      U 数据
     * @param v      V 数据
     * @param nv21   生成的nv21，需要预先分配内存
     * @param stride 步长
     * @param height 图像高度
     */
    public static void yuv420ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {
        System.arraycopy(y, 0, nv21, 0, y.length);
        // 注意，若length值为 y.length * 3 / 2 会有数组越界的风险，需使用真实数据长度计算
        int length = y.length + u.length + v.length;
        int uIndex = 0, vIndex = 0;
        for (int i = stride * height; i < length; i++) {
            nv21[i] = v[vIndex++];
            nv21[i + 1] = u[uIndex++];
        }
    }

    public static Bitmap nv21ToBitmap(byte[] nv21, int w, int h) {
        YuvImage image = new YuvImage(nv21, ImageFormat.NV21, w, h, null);
        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
            image.compressToJpeg(new Rect(0, 0, w, h), 100, stream);
            return BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * YUV420888转NV21格式数据-方式1
     */
    public static @Nullable byte[] bufferToNv21_Mode1(@NonNull ImageProxy image) {
        if (ImageFormat.YUV_420_888 != image.getFormat()) return null;

        int width = image.getWidth();
        int height = image.getHeight();
        ImageProxy.PlaneProxy[] planes = image.getPlanes();
        ByteBuffer yOriginalBuffer = planes[0].getBuffer();
        ByteBuffer uOriginalBuffer = planes[1].getBuffer();
        ByteBuffer vOriginalBuffer = planes[2].getBuffer();
        byte[] nv21;
        /*第一种方式*/
        ByteBuffer yBuffer = getBufferWithoutPadding(yOriginalBuffer, width, planes[0].getRowStride(), height, false);
        ByteBuffer vBuffer;
        //part1 获得真正的消除padding的yBuffer和uBuffer。需要对P格式和SP格式做不同的处理。如果是P格式的话只能逐像素去做，性能会降低。
        if (planes[2].getPixelStride() == 1) { //如果为true，说明是P格式。
            vBuffer = getUVBufferWithoutPaddingP(uOriginalBuffer, vOriginalBuffer,
                    width, height, planes[1].getRowStride(), planes[1].getPixelStride());
        } else {
            vBuffer = getBufferWithoutPadding(vOriginalBuffer, width, planes[2].getRowStride(), height / 2, true);
        }

        //part2 将y数据和uv的交替数据（除去最后一个v值）赋值给nv21
        int ySize = yBuffer.remaining();
        int vSize = vBuffer.remaining();
        int byteSize = width * height * 3 / 2;
        nv21 = new byte[byteSize];
        yBuffer.get(nv21, 0, ySize);
        vBuffer.get(nv21, ySize, vSize);

        //part3 最后一个像素值的u值是缺失的，因此需要从u平面取一下。
        byte lastValue = uOriginalBuffer.get(uOriginalBuffer.capacity() - 1);
        nv21[byteSize - 1] = lastValue;

        return nv21;
    }

    /**
     * YUV420888转NV21格式数据-方式2
     */
    public static @Nullable byte[] bufferToNv21_Mode2(@NonNull ImageProxy image) {
        if (ImageFormat.YUV_420_888 != image.getFormat()) return null;

        int width = image.getWidth();
        int height = image.getHeight();
        ImageProxy.PlaneProxy[] planes = image.getPlanes();
        ByteBuffer yBuffer = planes[0].getBuffer();
        ByteBuffer uBuffer = planes[1].getBuffer();
        ByteBuffer vBuffer = planes[2].getBuffer();

        int ySize = yBuffer.remaining();
        int vSize = vBuffer.remaining();
        byte[] nv21 = new byte[width * height * 3 / 2];
        int uSize = uBuffer.remaining();
        yBuffer.get(nv21, 0, ySize);
        vBuffer.get(nv21, ySize, vSize);

        byte[] u = new byte[uSize];
        uBuffer.get(u);

        //每隔开一位替换V，达到VU交替
        int pos = ySize + 1;
        for (int i = 0; i < uSize; i++) {
            if (i % 2 == 0) {
                nv21[pos] = u[i];
                pos += 2;
            }
        }
        return nv21;
    }

    /**
     * YUV420888转NV21格式数据-方式3
     */
    public static @Nullable byte[] yuv420888ToNv21_Mode3(@NonNull ImageProxy image) {
        if (ImageFormat.YUV_420_888 != image.getFormat()) return null;
        ImageProxy.PlaneProxy[] planes = image.getPlanes();
        ByteBuffer yBuffer = planes[0].getBuffer();
        ByteBuffer uBuffer = planes[1].getBuffer();
        ByteBuffer vBuffer = planes[2].getBuffer();

        int ySize = yBuffer.remaining();
        int vSize = vBuffer.remaining();
        int uSize = uBuffer.remaining();

        byte[] nv21 = new byte[ySize + uSize + vSize];
        yBuffer.get(nv21, 0, ySize);
        vBuffer.get(nv21, ySize, vSize);
        uBuffer.get(nv21, ySize + vSize, uSize);
        return nv21;
    }
}